package com.zhongkun.datahome.framework.aspectj;

import com.zhongkun.datahome.common.core.redis.RedisCache;
import com.zhongkun.datahome.common.exception.ServiceException;
import com.zhongkun.datahome.framework.redis.RedisLock;
import com.zhongkun.datahome.framework.redis.RedisLockKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * redis锁
 *
 * @author lidongzhang
 */
@Component
@Aspect
@Slf4j
@Order(9999)
public class RedisLockAspect {

    @Resource
    private RedisCache redisCache;

    private static final String REDIS_LOCK_KEY_PREFIX = "redis_lock_data_home";

    @Pointcut(value = "@annotation(com.zhongkun.datahome.framework.redis.RedisLock)")
    public void restAction() {
    }

    @Around(value = "restAction()")
    public Object restActionLog(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        RedisLock redisLock = getRedisLock(signature);
        // 无锁
        if (redisLock == null) {
            return joinPoint.proceed();
        }

        String lockRedisKey = getRedisLockKey(joinPoint, redisLock);
        String lockId = lock(lockRedisKey, redisLock);
        try {
            //执行业务方法
            return joinPoint.proceed();
        } finally {
            //释放锁
            unlock(lockRedisKey, lockId);
        }
    }

    private RedisLock getRedisLock(MethodSignature signature) {
        for (Annotation annotation : signature.getMethod().getAnnotations()) {
            if (annotation instanceof RedisLock) {
                return (RedisLock) annotation;
            }
        }
        return null;
    }

    private String getRedisLockKey(ProceedingJoinPoint pjp, RedisLock redisLock) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();

        String lockKey = null;
        Object[] args = pjp.getArgs();
        for (Object arg : args) {
            if (arg instanceof RedisLockKey) {
                lockKey = ((RedisLockKey) arg).getLockKey();
            }
        }

        StringBuilder sb = new StringBuilder();
        sb.append(REDIS_LOCK_KEY_PREFIX);

        //指定了锁名
        if (StringUtils.isNotBlank(redisLock.lockName())) {
            sb.append(redisLock.lockName());
        } else {
            //没指定，则用类名+方法名
            sb.append(className).append(":").append(methodName);
        }

        if (lockKey != null) {
            sb.append(":").append(lockKey);
        }

        return sb.toString();
    }

    /**
     * 上锁，失败会抛出异常
     *
     * @param lockRedisKey 锁的key
     * @param redisLock    锁注解
     */
    private String lock(String lockRedisKey, RedisLock redisLock) {
        String lockId = UUID.randomUUID().toString();
        long expireTime = System.currentTimeMillis() + redisLock.timeout() * 1000;
        while (System.currentTimeMillis() < expireTime) {
            //获取锁
            boolean lock = redisCache.setCacheNx(lockRedisKey, lockId, redisLock.timeout(), TimeUnit.SECONDS);
            //获取锁成功
            if (lock) {
                return lockId;
            }
            //获取锁失败
            else {
                if (redisLock.retry()) {
                    log.warn("get lock fail, retry after 1s. key:{}", lockRedisKey);
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        log.error("sleep error", e);
                        Thread.currentThread().interrupt();
                    }

                } else {
                    log.warn("get lock fail, return false. key:{}", lockRedisKey);
                    throw new ServiceException("当前操作过于频繁，请稍后再试");
                }
            }
        }
        throw new ServiceException("过期请求");
    }

    /**
     * 释放锁
     *
     * @param lockRedisKey 锁的key
     * @param lockId       锁id
     */
    private void unlock(String lockRedisKey, String lockId) {
        //仅可释放自己加的锁
        if (lockId.equals(redisCache.getCacheObject(lockRedisKey))) {
            redisCache.deleteObject(lockRedisKey);
        }
    }
}
